<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        html,
        body {
            height: 100%;
        }
        
        body {
            background: #333;
        }
        
        canvas {
            display: block;
            margin: 20px auto;
        }
    </style>
</head>

<body>
    <canvas width="1000" height="500"></canvas>
    <script>
        var cas = document.querySelector('canvas');
        var ctx = cas.getContext('2d');

        /* ---------------  主要弧形绘制工具  单弧形  -------------------- */
        function Arc(option) {
            if (this === window) return new Arc(option);
            //基本参数设置
            this.cas = option.cas;
            this.ctx = this.cas.getContext('2d');
            this.x = option.data[0];
            this.y = option.data[1];
            this.radius = option.data[2];

            //如果是用比率绘制数据圆形
            if (option.rateArr) {
                this.rateArr = option.rateArr;
                this.colors = option.colors;
                //如果有间距
                if (option.gap) this.gap = option.gap;
                //如果设置了rRate
                if (option.rRate) this.rRate = option.rRate;
            } else {
                this.start = option.data[3];
                this.end = option.data[4];
            }
            //默认填充为填充弧形  （楔形）
            //如果传入了线型  则表示要设置为空心楔形
            if (option.stroke) {
                this.stroke = option.stroke;
                //默认填充颜色为闪蓝色
            } else this.fill = option.fill || 'dodgerblue';
            //如果有写字
            if (option.txt || option.txtArr) {
                //如果是单个字
                if (option.txt) this.txt = option.txt;
                //如果是字组
                if (option.txtArr) this.txtArr = option.txtArr;
                //字体样式
                if (option.font) this.font = option.font;
                //设置线宽
                if (option.lineWidth) this.lineWidth = option.lineWidth;
                //线样式
                if (option.lineStroke) this.lineStroke = option.lineStroke;
                //线超出长度
                if (option.distance) this.distance = option.distance;
                //字偏移值
                if (option.txtPadding) this.txtPadding = option.txtPadding;
            }
        }
        Arc.prototype = {
            constructor: Arc,
            draw: function () {
                //参数
                var cas = this.cas,
                    ctx = this.ctx,
                    x = this.x,
                    y = this.y,
                    r = this.radius;
                //如果是用比率绘制圆形
                if (this.rateArr) {
                    var rateArr = this.rateArr,
                        colors = this.colors;
                    if (this.txtArr) var txtArr = this.txtArr;
                } else {
                    //单个绘制
                    var start = this.start,
                        end = this.end;
                    if (this.txt) var txt = this.txt;
                }
                //保存样式
                ctx.save();
                ctx.beginPath();
                if (rateArr) {
                    /* -----------------  多个数据进行绘制  ------------------ */
                    //遍历比率进行绘制
                    start = -90; //以-90度为起点
                    //对每一个楔形进行绘制
                    //如果设置了间距
                    var gap = this.gap || 0,
                        resDeg = 360 - gap * (rateArr.length);

                    //rRate  半径根据权重进行增加
                    var rRate = this.rRate || 0;

                    //遍历比例数组 循环生成arc
                    for (var i = 0; i < rateArr.length; i++) {
                        //计算结束度数
                        start += gap;
                        end = start + rateArr[i] * resDeg;
                        //如果要写文字
                        if (txtArr) {
                            //如果有样式
                            if (this.font) var font = this.font;
                            //线样式
                            if (this.lineStroke) var lineStroke = this.lineStroke;
                            //线宽
                            if (this.lineWidth) var lineWidth = this.lineWidth;
                            //线超出长度
                            if (this.distance) var distance = this.distance;
                            //字偏移值
                            if (this.txtPadding) var txtPadding = this.txtPadding;

                            //利用Arc递归来一一绘制
                            
                            Arc({
                                cas: cas,
                                data: [x, y, r * (1 + rateArr[i] * rRate), start, end],
                                fill: colors[i],
                                txt: txtArr[i],
                                font: font,
                                lineStroke: lineStroke,
                                lineWidth: lineWidth,
                                txtPadding: txtPadding,
                                distance: distance
                            }).draw();

                        } else {
                            //不写文字  只是图形绘制
                            Arc({
                                cas: cas,
                                data: [x, y, r, start, end],
                                fill: colors[i],
                            }).draw();
                        }
                        //把当前的结束度数设置为下一个的起始度数
                        start = end;
                    }

                } else {
                    //开始绘制单个
                    if (this.stroke) {
                        //如果是空心楔形
                        //样式
                        ctx.strokeStyle = this.stroke;
                        if (this.lineWidth) ctx.lineWidth = this.lineWidth;
                        ctx.moveTo(
                            x + r * Math.cos(toRadian(start)),
                            y + r * Math.sin(toRadian(start))
                        );
                        ctx.arc(x, y, r, toRadian(start), toRadian(end));
                        ctx.stroke();
                    } else {
                        //如果是楔形
                        ctx.fillStyle = this.fill;
                        ctx.moveTo(x, y);
                        ctx.arc(x, y, r, toRadian(start), toRadian(end));
                        ctx.fill();
                    }
                    if (txt) {
                        //如果有文字 绘制在楔形的中间  延长线
                        //保存样式
                        ctx.save();
                        ctx.beginPath();
                        //设置文字样式

                        ctx.strokeStyle = this.lineStroke || this.fill;
                        ctx.font = this.font || '15px Consoles';
                        //文字颜色 默认为他对应的填充颜色
                        ctx.fillStyle = this.txtColor || this.fill;
                        if (this.lineWidth) ctx.lineWidth = this.lineWidth;

                        //线超出楔形的距离   文字偏移距离  
                        var distance = this.distance || 20,
                            txtPadding = this.txtPadding || 8,
                            //得到文字底线长度    当前角大小
                            txtLine = ctx.measureText(txt).width + 2 * txtPadding,
                            angle = start + (end - start) * .5;
                        //得到交点坐标
                        x1 = x + (r + distance) * Math.cos(toRadian(angle)),
                            y1 = y + (r + distance) * Math.sin(toRadian(angle));
                        //移动到中点
                        ctx.moveTo(x, y);
                        //对txtLine方向判定
                        if (angle > 90 && angle < 270) txtLine = -txtLine;
                        //画线
                        ctx.lineTo(x1, y1);
                        ctx.lineTo(x1 + txtLine, y1);
                        //写字
                        //设置字的对其方式
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'bottom';
                        //执行写字
                        ctx.fillText(txt, x1 + txtLine * .5, y1 - txtPadding);
                        ctx.stroke();
                        //恢复所保存的样式
                        ctx.restore();
                    }
                }
                
                //恢复样式
                ctx.restore();
                //可以链式操作
                return this;

                //弧度转换封装
                function toRadian(angle) {
                    return angle * Math.PI / 180;
                }

            }
        }

        /* ----------------  扇形数据可视化工具  整圆图形 ------------------*/
        function ArcData(option) {
            if (this === window) return new ArcData(option);

            //自定义一个颜色组  随机设置
            var colors = [
                ['#66CCCC', '#CCFF66', '#FF99CC', '', '#339999', '#FFFF00', 'seagreen', 'whitesmoke'],
                ['#FFFFCC', '#CCFFFF', '#FFCCCC', '#99CCCC', '#FFCC99', '#FFCCCC', '#FF9999',
                    '#996699'
                ],
                ['#CC9999', '#FFCCCC', '#666699', '#009999', '#66CCCC', '#CCFFFF', 'seagreen',
                    'whitesmoke'
                ]
            ];

            //基本参数
            this.cas = option.cas;
            this.ctx = this.cas.getContext('2d');
            this.x = option.arc[0];
            this.y = option.arc[1];
            this.r = option.arc[2];
            this.data = option.data;

            //如果要设置间隔度数
            if (option.gap) this.gap = option.gap;

            //如果设置了 rRate
            if (option.rRate) this.rRate = option.rRate;
            //如果要写字
            if (option.txtArr) {
                this.txtArr = option.txtArr;
                //字体
                if (option.font) this.font = option.font;
                //线样式
                if (option.lineStroke) this.lineStroke = option.lineStroke;
                //设置线宽
                if (option.lineWidth) this.lineWidth = option.lineWidth;
                //线超出长度
                if (option.distance) this.distance = option.distance;
                //字偏移值
                if (option.txtPadding) this.txtPadding = option.txtPadding;

            };
            //颜色数组
            this.colors = option.colors || colors[Math.floor(Math.random() * colors.length)];
        }
        ArcData.prototype = {
            constructor: ArcData,
            draw: function () {
                var cas = this.cas,
                    ctx = this.ctx,
                    x = this.x,
                    y = this.y,
                    r = this.r,
                    data = this.data,
                    colors = this.colors;
                if (this.txtArr) var txtArr = this.txtArr;
                //开始绘制
                //保存样式
                ctx.save();
                ctx.beginPath();

                //如果有间隔
                var gap = this.gap;
                //如果设置了rRate
                var rRate = this.rRate;


                /* ------------------ 计算份额 --------------------- */
                var sum = 0;
                data.forEach(function (v, i) {
                    sum += v;
                });
                //百分比数组所占百分比
                var rateArr = data.map(function (v, i) {
                    return v / sum;
                });
                // console.log(rateArr);
                //计算画出楔形
                //绘制楔形
                //如果要写字
                if (txtArr) {
                    if (this.font) var font = this.font;
                    //线样式
                    if (this.lineStroke) var lineStroke = this.lineStroke;
                    //线宽
                    if (this.lineWidth) var lineWidth = this.lineWidth;
                    //线超出长度
                    if (this.distance) var distance = this.distance;
                    //字偏移值
                    if (this.txtPadding) var txtPadding = this.txtPadding;

                    //调用单独楔形进行绘制
                    Arc({
                        cas: cas,
                        data: [x, y, r],
                        rRate:rRate,
                        rateArr: rateArr,
                        gap: gap,
                        colors: colors,
                        txtArr: txtArr,
                        font: font,
                        lineStroke: lineStroke,
                        lineWidth: lineWidth,
                        txtPadding: txtPadding,
                        distance: distance
                    }).draw();
                } else {
                    Arc({
                        cas: cas,
                        data: [x, y, r],
                        rateArr: rateArr,
                        gap: gap,
                        colors: colors,
                    }).draw();
                }
            }
        };

        /* -----------------   使用说明   -------------------------------- */

        /*
            1、建立楔形（单）    Arc(option);
                @param   cas            -------   对应的canvas对象               （必有）
                @param   data           -------   对应的楔形参数   [x, y, r, start, end]     （必有）
                @param   fill           -------   填充样式                 默认 'dodgerblue'
                @param   stroke         -------   使用空心楔形              
                @param   txt            -------   对应当前楔形的介绍字
                @param   font           -------   介绍字体                 默认 '15px Consoles'
                @param   txtPadding     -------   字体偏移距离              默认 8
                @param   lineWidth      -------   设置线宽                 默认 1
                @param   distance       -------   写字区域离圆弧距离        默认 20
                
                方法： .draw()      在cas中进行渲染

            2、根据数据进行数据可视化   自行计算所占比率  ArcData(option);
                @param   cas           -------   对应的canvas对象               （必有）
                @param   arc           -------   对应的楔形参数（起止自动计算）   [x, y, r]     （必有）
                @param   data          -------   每部分所对应的数据             （必有）
                @param   txtArr        -------   对应当前楔形的介绍字（与data数据一一对应）     （必有）
                @param   gap           -------   每个楔形的中间间距             (默认无)
                @param   rRate         -------   设置根据data权重的半径增加     （默认0）
                @param   font          -------   介绍字体                   默认 '15px Consoles'
                @param   lineWidth     -------   线宽                      默认 1
                @param   txtPadding    -------   字体偏移距离               默认 8
                @param   distance      -------   写字区域离圆弧距离         默认 20
                @param   colors        -------   对应的颜色    （与data数据一一对应）（函数中默认有颜色）

                方法：  .draw()       在cas中进行渲染

        */

        /* ------------------- 单个测试 ----------------------------- */
        Arc({
            cas: cas,
            data: [200, 200, 50, -90, 10],
            txt: '第一个',
            font: '20px 隶书',
            txtPadding: 20,
            lineWidth: 2,
            distance: 30
        }).draw();

        Arc({
            cas: cas,
            data: [200, 200, 80, 20, 120],
            txt: '第二个',
            font: '20px 隶书',
            fill: '#ff6666',
            txtPadding: 20,
            distance: 30
        }).draw();

        Arc({
            cas: cas,
            data: [200, 200, 50, 130, 200],
            txt: '第三个',
            font: '20px 隶书',
            fill: '#dddd55',
            txtPadding: 20,
            distance: 30
        }).draw();


        /* -------------------- 数据可视化 ---------------------- */
        ArcData({
            cas: cas,
            arc: [700, 300, 50],
            rRate: 3,
            lineWidth: 3,
            //gap: 10,
            data: [1, 2, 3, 4, 5],
            txtArr: ['第一个', '第二个', '第三个', '第四个', '第五个', ],
            font: "17px microsoft yahei",
            distance: 100,
            txtPadding: 30
        }).draw();
    </script>
</body>

</html>